前面介紹了不少 Resource 到 httpResource() 的應用。不過在 Angular 開發中,Observable 與 RxJS 仍然是很常見的非同步資料處理方式,像是 switchMap、map、debounceTime 這類 Operator,在實務上都很常用。
如果原本的資料來源就是 Observable,又希望使用 Resource 提供的狀態管理方式,就可以使用 rxResource()。
和前面介紹的 resource()、httpResource() 用法相比,rxResource() 的資料來源是 Observable,因此可以沿用既有的 RxJS 流程,再由 Resource 管理載入狀態、錯誤狀態與資料結果。

resource() 和 rxResource() 都可以用來管理非同步資料,主要差別在於資料來源的形式。
前面介紹 resource() 時,我們主要透過 loader 處理 Promise-based 的非同步流程。
例如使用 fetch():
userResource = resource({
params: () => ({
userId: this.userId(),
}),
loader: async ({ params }) => {
const response = await fetch(
`https://dummyjson.com/users/${params.userId}`
);
return response.json();
},
});
fetch() 本身會回傳 Promise,因此可以直接交給 resource() 處理。
如果原本的 Service 回傳 Observable,例如使用 Angular HttpClient:
getUser(userId: number) {
return this.http.get<User>(
`https://dummyjson.com/users/${userId}`
);
}
這時就可以改用 rxResource():
userResource = rxResource({
params: () => ({
userId: this.userId(),
}),
stream: ({ params }) => {
return this.userService.getUser(params.userId);
},
});
rxResource() 透過 stream 接收 Observable,因此也可以繼續搭配既有的 RxJS Operator:
userResource = rxResource({
params: () => ({
userId: this.userId(),
}),
stream: ({ params }) => {
return this.userService.getUser(params.userId).pipe(
map(user => ({
...user,
fullName: `${user.firstName} ${user.lastName}`,
}))
);
},
});
因此,rxResource() 並不是只能搭配 HttpClient 使用。
只要資料來源本身是 Observable,例如 WebSocket、計時器,或其他以 Observable 提供資料的 API,都可以交給 rxResource() 管理。
使用 rxResource() 時有一點需要特別注意:stream 回傳的 Observable 在完成前,必須至少發出一個值或錯誤。
例如:
userResource = rxResource<User>({
params: () => ({
userId: this.userId(),
}),
stream: ({ params }) => {
return this.userService.getUser(params.userId).pipe(
catchError(() => EMPTY)
);
},
});
當 getUser() 發生錯誤時,catchError() 會攔截錯誤,接著改成 EMPTY。
但 EMPTY 不會發出任何值,也不會再拋出錯誤,而是直接完成 Observable。
對 rxResource() 來說,這次載入沒有產生任何結果,因此 Angular 會拋出 RESOURCE_COMPLETED_BEFORE_PRODUCING_VALUE 錯誤。
如果希望錯誤發生時提供替代結果,可以改成:
userResource = rxResource<User | null>({
params: () => ({
userId: this.userId(),
}),
stream: ({ params }) => {
return this.userService.getUser(params.userId).pipe(
catchError(() => of(null))
);
},
});
這樣 Request 發生錯誤時,Observable 仍然會發出 null,Resource 也能取得這次的結果。
如果希望保留 Resource 本身的 Error 狀態,就不要在 stream 裡把錯誤轉成 EMPTY 或其他替代值,而是讓錯誤繼續往外傳遞。
rxResource() 和 resource() 一樣,也可以透過 params 決定目前是否要開始載入資料。
當 params 回傳 undefined 時,Resource 不會執行 stream。
例如有些情況需要等 userId 準備完成後才發出 Request:
userId = signal<number | undefined>(undefined);
userResource = rxResource({
params: () => {
const userId = this.userId();
if (userId === undefined) {
return undefined;
}
return { userId };
},
stream: ({ params }) => {
return this.userService.getUser(params.userId);
},
});
當 userId 還是 undefined 時,params 會直接回傳 undefined,Resource 維持在 idle,也不會執行 stream。
等到 userId 有值:
this.userId.set(1);
params 才會產生資料,接著執行 stream 取得使用者資訊。
這種寫法很適合用在需要等待必要條件準備完成後,才開始載入資料的情境。
不過這裡要注意,必須是 params 本身回傳 undefined:
params: () => undefined
如果只是物件中的欄位是 undefined:
params: () => ({
userId: undefined,
})
對 Resource 來說,這仍然是一個有效的參數物件,因此 stream 還是會執行,只是 params.userId 的值剛好是 undefined。
所以可以先簡單區分:
resource():前面介紹的用法主要透過 loader 處理 Promise-based 的非同步資料rxResource():透過 stream 接收 Observable-based 的非同步資料rxResource() 使用 stream 定義資料來源,可以把它理解成前面 resource() 中 loader 的 Observable 版本。
當 params 改變時,rxResource() 會重新執行 stream,並改用新的 Observable。
這種「條件改變後,切換到最新資料流」的行為,和 RxJS 的 switchMap 很接近。
假設使用 switchMap 根據 userId$ 取得使用者資料:
user$ = this.userId$.pipe(
switchMap(userId => {
return this.userService.getUser(userId);
})
);
每當 userId$ 發出新值,switchMap 都會取消前一個內層 Observable 的訂閱,再訂閱新的 Observable。
假設使用者快速切換 userId,而前面的 Request 還沒完成:
userId = 1 → 請求 1(取消訂閱)
userId = 2 → 請求 2(取消訂閱)
userId = 3 → 請求 3(保留)
最後會以 userId = 3 的資料流為主,前面的結果不會再影響目前的資料。
換成 rxResource(),可以直接使用 Signal 作為 params 的來源:
userId = signal(1);
userResource = rxResource({
params: () => ({
userId: this.userId(),
}),
stream: ({ params }) => {
return this.userService.getUser(params.userId);
},
});
當 userId 改變:
this.userId.set(2);
// 請求還沒完成,又切換到 3
this.userId.set(3);
params 會重新計算,stream 也會根據新的參數建立新的 Observable。
因此最後會以 userId = 3 對應的資料流為主,前面尚未完成的資料流不會再成為目前 Resource 的資料來源。
雖然這個行為和 switchMap 很接近,但兩者處理的層次並不相同。
switchMap 處理的是 Observable Pipeline 中「來源改變後,要切換到哪一個 Observable」的問題。
rxResource() 則是把 Observable 資料來源接進 Resource 的模型,同時提供:
value()
isLoading()
error()
status()
如果原本就在 RxJS Pipeline 中處理多段 Observable 的轉換、組合或切換,可以繼續使用 switchMap 等 Operator。
如果資料來源本身是 Observable,而畫面還需要管理載入狀態、錯誤狀態與目前資料,就可以再交給 rxResource()。
因此,rxResource() 並不是用來取代 switchMap。
RxJS 仍然負責 Observable 資料流的轉換、組合與切換;rxResource() 則負責把最後的 Observable 接進 Resource,統一管理載入狀態、錯誤狀態與資料結果。

rxResource() 補上了 Observable 與 Resource 之間的連接方式。原本已經使用 RxJS 的資料流,不需要為了使用 Resource 改寫成 Promise,資料的轉換、組合與錯誤處理仍然可以留在 RxJS Pipeline 中,再把最後的 Observable 交給 stream。
接著由 rxResource() 管理這筆非同步資料的狀態,例如載入中、成功、失敗,以及目前取得的值。使用 catchError() 時,也可以依照需求決定錯誤要交給 Resource,還是在 Observable Pipeline 中轉成 null 或其他替代結果。
不過,也不是每個 Observable 都需要使用 rxResource()。如果只是想把 Observable 轉成 Signal,又不需要額外管理載入與錯誤狀態,使用 toSignal() 就足夠。
如果還需要處理多個 Observable 之間的轉換、組合或切換,可以繼續交給 RxJS Operator;當最後希望把這筆資料交給畫面使用,並一起管理 loading、error 與 value 時,再交給 rxResource()。
rxResource() 透過 stream 接收 Observable。params 回傳 undefined 時,Resource 會維持 idle。rxResource() 會拋出的錯誤。